
package com.hex.ds.code.detection.publish.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hex.ds.code.detection.common.entity.ClusterSparingConf;
import com.hex.ds.code.detection.config.dao.ConfigPathGitMapper;
import com.hex.ds.code.detection.pack.entity.ScriptPackScanListEntity;
import com.hex.ds.code.detection.pack.enums.FileScanEnum;
import com.hex.ds.code.detection.pack.service.IScriptPackScanListService;
import com.hex.ds.code.detection.pack.service.IScriptPackTaskService;
import com.hex.ds.code.detection.publish.dao.ScriptPublishTaskMapper;
import com.hex.ds.code.detection.common.base.PageResult;
import com.hex.ds.code.detection.common.dao.ClusterSparingConfMapper;
import com.hex.ds.code.detection.common.service.ClusterSparingConfService;
import com.hex.ds.code.detection.config.service.ConfigPropertyJdbcconfigService;
import com.hex.ds.code.detection.publish.entity.ScriptPublishListEntity;
import com.hex.ds.code.detection.publish.entity.ScriptPublishTaskEntity;
import com.hex.ds.code.detection.publish.service.IPublishTaskService;
import com.hex.ds.code.detection.publish.service.IScriptPublishListService;
import com.hex.ds.code.detection.publish.service.IScriptPublishResultService;
import com.hex.ds.code.detection.publish.service.IScriptPublishTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.sql.DataSource;
import java.io.File;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 脚本发布任务
 *
 * @author Wang zhihao
 * @date 2023-09-13 18:16:07
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ScriptPublishTaskServiceImpl extends ServiceImpl<ScriptPublishTaskMapper, ScriptPublishTaskEntity> implements IScriptPublishTaskService {

    @Value("${script.pack.relativePath}")
    private String relativePath;

    @Value("${script.scan.periodFilePath}")
    private String periodFilePath;

//    @Value("${script.publish.configFilePath}")
//    private String configFilePath;

    private final ClusterSparingConfService clusterSparingConfService;

    private final IScriptPackScanListService scriptPackScanListService;

    private final IScriptPublishListService scriptPublishListService;

    private final IScriptPublishResultService scriptPublishResultService;

    private final IPublishTaskService publishTaskService;

    private final ScriptPublishTaskMapper scriptPublishTaskMapper;

    private final ConfigPropertyJdbcconfigService configPropertyJdbcconfigService;

    private final ClusterSparingConfMapper clusterSparingConfMapper;

    private final IScriptPackTaskService scriptPackTaskService;

    private final ConfigPathGitMapper configPathGitMapper;


    /**
     * @Method queryList
     * @Param
     * @param page
     * @param scriptPublishTask
     * @Return com.hex.ds.code.detection.common.base.PageResult
     * @Description 分页查询脚本发布任务并返回PageResult类型的数据
     * @Author Wang zhihao
     * @Date 2023/9/14 17:11
     * @Version V1.0
     */
    @Override
    public PageResult queryList(Page page, ScriptPublishTaskEntity scriptPublishTask) {
        LambdaQueryWrapper<ScriptPublishTaskEntity> wrapper = beforeQuery(scriptPublishTask);
        Page returnPage = baseMapper.selectPage(page, wrapper);
        List<ScriptPublishTaskEntity> scriptPublishTasks = returnPage.getRecords();
        List<ClusterSparingConf> clusterSparingConfs = clusterSparingConfService.queryList(new Page(), new ClusterSparingConf()).getRecords();
        // 获取集群ID与名称的Map映射
        Map<String, String> clusterMap = clusterSparingConfs.stream().collect(Collectors.toMap(ClusterSparingConf::getId, ClusterSparingConf::getClusterName));
        for (ScriptPublishTaskEntity scriptPublish : scriptPublishTasks) {
            if (CollectionUtil.isNotEmpty(scriptPublish.getPublishClusterList())) {
                String clusterName = scriptPublish.getPublishClusterList().stream().map(clusterMap::get).collect(Collectors.joining(","));
                scriptPublish.setPublishClusterNames(clusterName);
            }
        }
        return new PageResult(scriptPublishTasks, returnPage.getTotal());
    }

    /**
     * @Method queryListNoPage
     * @Param
     * @param scriptPublishTask
     * @Return com.hex.ds.code.detection.script.publish.entity.ScriptPublishTaskEntity
     * @Description 不分页查询
     * @Author Wang zhihao
     * @Date 2023/11/29 18:19
     * @Version V1.0
     */
    @Override
    public List<ScriptPublishTaskEntity> queryListNoPage(ScriptPublishTaskEntity scriptPublishTask) {
        LambdaQueryWrapper<ScriptPublishTaskEntity> wrapper = beforeQuery(scriptPublishTask);
        return baseMapper.selectList(wrapper);
    }

    /**
     * @Method beforeQuery
     * @Param
     * @param scriptPublishTask
     * @Return com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.hex.ds.code.detection.script.publish.entity.ScriptPublishTaskEntity>
     * @Description 搜索前的条件配置
     * @Author Wang zhihao
     * @Date 2023/12/5 13:31
     * @Version V1.0
     */
    private LambdaQueryWrapper<ScriptPublishTaskEntity> beforeQuery(ScriptPublishTaskEntity scriptPublishTask) {
        LambdaQueryWrapper<ScriptPublishTaskEntity> wrapper = Wrappers.lambdaQuery();
        if (StrUtil.isNotBlank(scriptPublishTask.getId())) {
            wrapper.eq(ScriptPublishTaskEntity::getId, scriptPublishTask.getId());
        }
        if (StrUtil.isNotBlank(scriptPublishTask.getPublishUser())) {
            wrapper.like(ScriptPublishTaskEntity::getPublishUser, scriptPublishTask.getPublishUser());
        }
        if (StrUtil.isNotBlank(scriptPublishTask.getPublishVersionId())) {
            wrapper.eq(ScriptPublishTaskEntity::getPublishVersionId, scriptPublishTask.getPublishVersionId());
        }
        if (StrUtil.isNotBlank(scriptPublishTask.getPublishState())) {
            wrapper.eq(ScriptPublishTaskEntity::getPublishState, scriptPublishTask.getPublishState());
        }
        if (StrUtil.isNotBlank(scriptPublishTask.getPublishClusterIds())) {
            wrapper.like(ScriptPublishTaskEntity::getPublishClusterIds, scriptPublishTask.getPublishClusterIds());
        }
        wrapper.orderByDesc(ScriptPublishTaskEntity::getCreateTime);
        return wrapper;
    }

    /**
     * @Method removeByIds
     * @Param
     * @param ids
     * @Return void
     * @Description 批量删除
     * @Author Wang zhihao
     * @Date 2023/9/14 17:23
     * @Version V1.0
     */
    @Override
    public void removeByIds(List<String> ids) {
        ids.forEach(this::removeById);
    }

    /**
     * @Method removeById
     * @Param
     * @param id
     * @Return void
     * @Description 根据主键删除
     * @Author Wang zhihao
     * @Date 2023/11/16 13:57
     * @Version V1.0
     */
    @Override
    public void removeById(String id) {
        this.baseMapper.deleteById(id);
        scriptPublishListService.removeByTaskId(id);
    }

    /**
     * @Method removeByVersionId
     * @Param
     * @param versionId
     * @Return void
     * @Description 根据版本ID删除
     * @Author Wang zhihao
     * @Date 2023/11/29 18:46
     * @Version V1.0
     */
    @Override
    public void removeByVersionId(String versionId) {
        List<ScriptPublishTaskEntity> scriptPublishTaskEntities = queryListNoPage(new ScriptPublishTaskEntity().setPublishVersionId(versionId));
        List<String> taskIds = scriptPublishTaskEntities.stream().map(ScriptPublishTaskEntity::getId).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(taskIds)) {
            removeByIds(taskIds);
        }
    }

    /**
     * @Method upload
     * @Param
     * @param multipartFileList
     * @Return java.util.List<java.io.File>
     * @Description 上传脚本
     * @Author Wang zhihao
     * @Date 2023/9/14 19:15
     * @Version V1.0
     */
    @Override
    public List<File> upload(List<MultipartFile> multipartFileList) {
        String uploadFilePath = getTmpFilePath("upload-file");
        String sourceFilePath = getTmpFilePath("source-file");
        try {
            for (MultipartFile multipartFile : multipartFileList) {
                File uploadFile = new File(uploadFilePath + File.separator + multipartFile.getOriginalFilename());
                FileUtil.mkdir(uploadFile);
                multipartFile.transferTo(uploadFile);
                if (FileUtil.getType(uploadFile).equalsIgnoreCase("zip")) {
                    ZipUtil.unzip(uploadFile, new File(sourceFilePath), Charset.forName("GBK"));
                } else {
                    FileUtil.copyFilesFromDir(uploadFile, new File(sourceFilePath + File.separator + multipartFile.getOriginalFilename()), true);
                }
            }
        } catch (Exception e) {
            log.error("文件上传失败:{}", e.getMessage(), e);
            throw new RuntimeException("文件上传失败！");
        }
        return null;
    }

    /**
     * @Method addPublishTask
     * @Param
     * @param scriptPublishTask
     * @Return void
     * @Description 添加发布任务
     * @Author Wang zhihao
     * @Date 2023/11/29 16:08
     * @Version V1.0
     */
    @Override
    public void addPublishTask(ScriptPublishTaskEntity scriptPublishTask) {
        try {
            scriptPublishTask.setDateFlag(DateUtil.format(DateUtil.date(), "yyMMddHHmm"));
            // 构建发布任务
            save(scriptPublishTask);
            // 构建发布清单
            int initTotal = 0;
            int serviceTotal = 0;
            int periodTotal = 0;
            for (String clusterId : scriptPublishTask.getPublishClusterList()) {
                ClusterSparingConf clusterSparingConf = clusterSparingConfService.getById(clusterId);
                scriptPublishTask.setPublishClusterNames(clusterSparingConf.getClusterName());
                // 构建初始化脚本发布清单
                initTotal += buildInitPublishList(scriptPublishTask, clusterSparingConf);
                // 构建推送下载稽核脚本发布清单
                serviceTotal += buildServicePublishList(scriptPublishTask, clusterSparingConf);
                // 构建周期脚本发布清单
                periodTotal += buildPeriodPublishList(scriptPublishTask, clusterSparingConf);
            }
            updateById(new ScriptPublishTaskEntity().setId(scriptPublishTask.getId()).setPublishState("PUBLISHING")
                    .setPublishTotal(initTotal + serviceTotal + periodTotal).setPublishSuccess(0).setPublishFail(0)
                    .setRestoreTotal(periodTotal).setRestoreSuccess(0).setRestoreFail(0));
            // 异步调用的方式执行发布任务
            publishTaskService.execPublishTask(scriptPublishTask);
        } catch (Exception e) {
            log.error("添加并执行发布任务异常: {}", e.getMessage(), e);
            throw new RuntimeException("添加并执行发布任务异常: " + e.getMessage(), e);
        }
    }

    /**
     * @Method buildInitPublishList
     * @Param
     * @param scriptPublishTask
     * @param clusterSparingConf
     * @Return void
     * @Description 构建初始化脚本发布清单
     * @Author Wang zhihao
     * @Date 2023/11/30 19:59
     * @Version V1.0
     */
    private Integer buildInitPublishList(ScriptPublishTaskEntity scriptPublishTask, ClusterSparingConf clusterSparingConf) {
        try {
            // 过滤出CDH的
            List<String> filePaths = getFilePaths(scriptPublishTask.getPackTaskId(), FileScanEnum.INIT.getValue()).stream()
                    .filter(s -> !"oracle".equalsIgnoreCase(s.split("/")[s.split("/").length-2])).collect(Collectors.toList());
            filePaths.forEach(filePath -> {
                // 添加记录到List表并返回添加list的主键
                addPublishListInfo(scriptPublishTask.getId(), scriptPublishTask.getServerIds(), filePath, "UNPUBLISHED", "init", scriptPublishTask.getDateFlag(), clusterSparingConf);
            });
            return filePaths.size();
        } catch (Exception e) {
            log.error("构建初始化脚本发布清单异常: {}", e.getMessage(), e);
            throw new RuntimeException("构建初始化脚本发布清单异常: " + e.getMessage(), e);
        }
    }

    /**
     * @Method buildServicePublishList
     * @Param
     * @param scriptPublishTask
     * @param clusterSparingConf
     * @Return void
     * @Description 构建推送下载稽核脚本发布清单
     * @Author Wang zhihao
     * @Date 2023/11/30 19:59
     * @Version V1.0
     */
    private Integer buildServicePublishList(ScriptPublishTaskEntity scriptPublishTask, ClusterSparingConf clusterSparingConf) {
        try {
            List<String> filePaths = getFilePaths(scriptPublishTask.getPackTaskId(), FileScanEnum.SERVICE.getValue());
            filePaths.forEach(filePath -> {
                addPublishListInfo(scriptPublishTask.getId(), scriptPublishTask.getServerIds(), filePath, "UNPUBLISHED", "service", scriptPublishTask.getDateFlag(), clusterSparingConf);
            });
            return filePaths.size();
        } catch (Exception e) {
            log.error("构建推送下载稽核脚本发布清单异常: {}", e.getMessage(), e);
            throw new RuntimeException("构建推送下载稽核脚本发布清单异常: " + e.getMessage(), e);
        }
    }

    /**
     * @Method buildPeriodPublishList
     * @Param
     * @param scriptPublishTask
     * @param clusterSparingConf
     * @Return void
     * @Description 构建周期脚本发布清单
     * @Author Wang zhihao
     * @Date 2023/11/30 19:59
     * @Version V1.0
     */
    private Integer buildPeriodPublishList(ScriptPublishTaskEntity scriptPublishTask, ClusterSparingConf clusterSparingConf) {
        try {
            // 获取周期脚本文件存储路径
            String cycleFilePath = scriptPublishTask.getMasterPackagePath() + periodFilePath;
            // 读取该路径下所有文件的绝对路径
            List<String> fileAbsolutePaths = FileUtil.loopFiles(cycleFilePath).stream().map(File::getAbsolutePath).collect(Collectors.toList());
            for (String fileAbsolutePath : fileAbsolutePaths) {
                addPublishListInfo(scriptPublishTask.getId(), scriptPublishTask.getServerIds(), fileAbsolutePath, "UNPUBLISHED", "period", scriptPublishTask.getDateFlag(), clusterSparingConf);
            }
            return fileAbsolutePaths.size();
        } catch (Exception e) {
            log.error("构建周期脚本发布清单: {}", e.getMessage(), e);
            throw new RuntimeException("构建周期脚本发布清单: " + e.getMessage(), e);
        }
    }

    /**
     * @Method restore
     * @Param
     * @param publishTaskId
     * @param clusterId
     * @Return void
     * @Description 还原
     * @Author Wang zhihao
     * @Date 2023/10/19 15:11
     * @Version V1.0
     */
    @Override
    public void restore(String publishTaskId, String clusterId) {
        ScriptPublishTaskEntity scriptPublishTaskInfo = getById(publishTaskId);
        ClusterSparingConf clusterSparingConf = clusterSparingConfService.getById(clusterId);
        clusterRestore(clusterSparingConf, scriptPublishTaskInfo);
    }

    /**
     * @Method modifyPublishSuccessNum
     * @Param
     * @param taskId
     * @Return void
     * @Description 更新成功数量
     * @Author Wang zhihao
     * @Date 2023/12/1 16:08
     * @Version V1.0
     */
    @Override
    public void modifyPublishSuccessNum(String taskId) {
        scriptPublishTaskMapper.modifyPublishSuccessNum(taskId);
        scriptPublishTaskMapper.modifyPublishStatus(taskId);
    }

    /**
     * @Method modifyPublishFailNum
     * @Param
     * @param taskId
     * @Return void
     * @Description 更新失败数量
     * @Author Wang zhihao
     * @Date 2023/12/1 16:08
     * @Version V1.0
     */
    @Override
    public void modifyPublishFailNum(String taskId) {
        scriptPublishTaskMapper.modifyPublishFailNum(taskId);
        scriptPublishTaskMapper.modifyPublishStatus(taskId);
    }

    /**
     * @Method modifyRestoreSuccessNum
     * @Param
     * @param taskId
     * @Return void
     * @Description 更新成功数量
     * @Author Wang zhihao
     * @Date 2023/12/1 16:08
     * @Version V1.0
     */
    @Override
    public void modifyRestoreSuccessNum(String taskId) {
        scriptPublishTaskMapper.modifyRestoreSuccessNum(taskId);
        scriptPublishTaskMapper.modifyRestoreStatus(taskId);
    }

    /**
     * @Method modifyRestoreFailNum
     * @Param
     * @param taskId
     * @Return void
     * @Description 更新失败数量
     * @Author Wang zhihao
     * @Date 2023/12/1 16:08
     * @Version V1.0
     */
    @Override
    public void modifyRestoreFailNum(String taskId) {
        scriptPublishTaskMapper.modifyRestoreFailNum(taskId);
        scriptPublishTaskMapper.modifyRestoreStatus(taskId);
    }

    /**
     * @Method clusterRestore
     * @Param
     * @param scriptPublishTask
     * @Return void
     * @Description 将发布的还原
     * @Author Wang zhihao
     * @Date 2023/10/19 15:14
     * @Version V1.0
     */
    private void clusterRestore(ClusterSparingConf clusterSparingConf, ScriptPublishTaskEntity scriptPublishTask) {
        // 远程集群还原
        updateById(scriptPublishTask.setPublishState("RESTORE"));
        remoteRestore(clusterSparingConf, scriptPublishTask);
    }

    /**
     * @Method remoteRestore
     * @Param
     * @param scriptPublishTask
     * @Return void
     * @Description 远程还原
     * @Author Wang zhihao
     * @Date 2023/10/19 17:01
     * @Version V1.0
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void remoteRestore(ClusterSparingConf clusterSparingConf, ScriptPublishTaskEntity scriptPublishTask) {
        try {
/*            // 获取连接源信息 (暂时关闭还原数据库信息的功能)
            DataSource impalaDataSource = dynamicImpalaDataSource(clusterSparingConf.getClusterName());

            // 还原初始化数据 (暂时关闭还原数据库信息的功能)
            restoreInitScript(scriptPublishTask, impalaDataSource, clusterSparingConf.getId());*/

            // 还原调度脚本文件
            restorePeriodScript(scriptPublishTask, clusterSparingConf);

        } catch (Exception e) {
            log.error("远程还原集群文件异常: {}", e.getMessage(), e);
            throw new RuntimeException("远程还原集群文件异常:" + e.getMessage(), e);
        }
    }

    /**
     * @Method restorePeriodScript
     * @Param
     * @param scriptPublishTask
     * @param clusterSparingConf
     * @Return void
     * @Description 还原周期脚本
     * @Author Wang zhihao
     * @Date 2023/11/22 10:12
     * @Version V1.0
     */
    private void restorePeriodScript(ScriptPublishTaskEntity scriptPublishTask, ClusterSparingConf clusterSparingConf) {
        List<ScriptPublishListEntity> scriptPublishListEntities = scriptPublishListService.queryListNoPage(
                new ScriptPublishListEntity().setScriptPublishTaskId(scriptPublishTask.getId()).setPublishType("period"));
        updateById(new ScriptPublishTaskEntity().setId(scriptPublishTask.getId()).setPublishState("RESTORING"));
        scriptPublishTaskMapper.modifyRestoreStatus(scriptPublishTask.getId());
        publishTaskService.execRestoreTask(scriptPublishTask, scriptPublishListEntities, clusterSparingConf);
    }

    /**
     * @Method addPublishListInfo
     * @Param
     * @param taskId
     * @param filePath
     * @param state
     * @param type
     * @param backupTime
     * @param clusterSparingConf
     * @Return java.lang.String
     * @Description 添加记录到List表并返回添加list的主键
     * @Author Wang zhihao
     * @Date 2023/11/13 15:48
     * @Version V1.0
     */
    private String addPublishListInfo(String taskId, String serverId, String filePath, String state, String type, String backupTime, ClusterSparingConf clusterSparingConf) {
        ScriptPublishListEntity scriptPublishList = new ScriptPublishListEntity()
                .setScriptPublishTaskId(taskId)
                .setFilePath(filePath.replace("\\","/"))
                .setFileName(FileNameUtil.getName(filePath))
                .setState(state)
                .setDateFlag(backupTime)
                .setPublishType(type)
                .setClusterId(clusterSparingConf.getId())
                .setServerId(serverId)
                .setClusterName(clusterSparingConf.getClusterName());
        scriptPublishListService.save(scriptPublishList);
        return scriptPublishList.getId();
    }

    /**
     * @Method getFilePaths
     * @Param
     * @param packTaskId
     * @param fileAffiliation
     * @Return java.util.List<java.lang.String>
     * @Description 根据发布任务与文件归属获取对应的文件路径
     * @Author Wang zhihao
     * @Date 2023/10/18 14:52
     * @Version V1.0
     */
    private List<String> getFilePaths(String packTaskId, String fileAffiliation) {
        ScriptPackScanListEntity scriptPackScanListEntity = new ScriptPackScanListEntity()
                .setScriptPackTaskId(packTaskId)
                .setFileAffiliation(fileAffiliation);
        List<ScriptPackScanListEntity> scriptPackScanListEntities = scriptPackScanListService.queryListNoPage(scriptPackScanListEntity);
        return scriptPackScanListEntities.stream().map(ScriptPackScanListEntity::getScanFilePath).collect(Collectors.toList());
    }

    /**
     * @Method executeSql
     * @Param
     * @param dataSource
     * @param sqls
     * @Return void
     * @Description 批量执行SQL
     * @Author Wang zhihao
     * @Date 2023/10/18 10:25
     * @Version V1.0
     */
    private void executeSql(DataSource dataSource, List<String> sqls) {
        Connection connection = null;
        Statement statement = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.createStatement();
            for (String sql : sqls) {
                statement.execute(sql);
            }
        } catch (Exception e) {
            log.error("执行SQL语句异常 {}", e.getMessage(), e);
            throw new RuntimeException("执行SQL语句异常" + e.getMessage(), e);
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
                if (statement != null) {
                    statement.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
                log.error("连接关闭异常 {}", e.getMessage(), e);
                throw new RuntimeException("连接关闭异常" + e.getMessage(), e);
            }
        }
    }

    /**
     * @Method getTmpFilePath
     * @Param
     * @param fileType
     * @Return java.lang.String
     * @Description 获取临时路径
     * @Author Wang zhihao
     * @Date 2023/9/14 19:21
     * @Version V1.0
     */
    private String getTmpFilePath(String fileType) {
        String filePath = StrUtil.format("{}/tmp/{}/{}/{}", relativePath, DateUtil.today(), fileType, System.currentTimeMillis());
        FileUtil.mkdir(filePath);
        return filePath;
    }


}
